﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Models
{
    /// <summary>
    /// Klasse til sprites der skal tegnes på skærmen, ideen med at have dem i seperat klase er 
    /// at så kan man tilgå dem fra både server og klient og jeg kan lave serialisering et sted
    /// </summary>
    public class Sprite
    {
        #region Fields
        //Texture data
        private int textureWidth;
        private int textureHeight;
        private Rectangle sourceRectangle;
        private int textureIndex;
        private int rows;
        private int columns;
        private int spriteIndex;

        //positionsdata
        private float offset;
        private Vector2 center;
        private Vector2 rotatingCenter;
        private Vector2 velocity;
        private float rotation;
        private float mass;
        private float rotationSpeed;

        //Skærm dimensioner
        Vector2 windowMax;

        //destruction Timer;
        private bool countDown = false;
        private float destructionTimer;

        //public fields
        public float Rotation { get { return rotation; } }
        public int Class { get { return textureIndex; } }
        public Vector2 Center { get { return center;  } }
        public float Radii { get { return (textureHeight + textureWidth) / 4; } }

        #endregion

        #region Constructors

        public Sprite(
            int textureIndex,
            Texture2D texture,
            int columns,
            int rows,
            int spriteIndex,
            Vector2 position,
            Vector2 velocity,
            float offset,
            float mass,
            float rotationSpeed,
            int windowWidth,
            int windowHeight
            ) : this(textureIndex,texture.Width,texture.Height,columns,rows,spriteIndex,position,velocity,offset,mass,rotationSpeed,windowWidth,windowHeight)
        {
        }

        public Sprite(
            int textureIndex,
            int textureWidth,
            int textureHeight,
            int columns,
            int rows,
            int spriteIndex,
            Vector2 position,
            Vector2 velocity,
            float offset,
            float mass,
            float rotationSpeed,
            int windowWidth,
            int windowHeight
            )
        {
            this.textureIndex = textureIndex;
            this.textureHeight = textureHeight;
            this.textureWidth = textureWidth;
            sourceRectangle = new Rectangle(0, 0, this.textureWidth, this.textureHeight);
            rotatingCenter = new Vector2(this.textureWidth / 2, this.textureHeight / 2);
            center = position;
            this.columns = columns;
            this.rows = rows;
            this.offset = offset;
            this.spriteIndex = spriteIndex;
            calculateSourceRectangle();
            this.velocity = velocity;
            windowMax = new Vector2(windowWidth, windowHeight);
            rotation = (float)Math.Atan2(velocity.Y, velocity.X);
            this.mass = mass;
            this.rotationSpeed = rotationSpeed;
        }

        #endregion

        #region Public Members

        /// <summary>
        /// Update funktion Endnu ikke implementeret
        /// </summary>
        public void Update(TimeSpan timeElapsed)
        {
            center += velocity * (float)timeElapsed.TotalSeconds;
            //center = Vector2.Clamp(center, Vector2.Zero, windowMax);
            if (center.X > windowMax.X)
            {
                center.X -= windowMax.X;
            }
            else if (center.X < 0)
            {
                center.X += windowMax.X;
            }
            if (center.Y > windowMax.Y)
            {
                center.Y -= windowMax.Y;
            }
            else if (center.Y < 0)
            {
                center.Y += windowMax.Y;
            }
            if (countDown)
            {
                destructionTimer -= (float)timeElapsed.TotalSeconds;
            }
            rotation += (float)timeElapsed.TotalSeconds * MathHelper.ToRadians(rotationSpeed);

        }

        public void Trottle(Vector2 pad, float timeElapsed)
        {
            //Acceleration
            float enginePowerForward = 250;
            float EnginePowerReverse = 100;
            float maxVelocity = 250;
            float power = 0;
            if (pad.Y > 0)
            {
                power = enginePowerForward * pad.Y;
            }
            else if (pad.Y < 0)
            {
                power = EnginePowerReverse * pad.Y;
            }
            Vector2 acceleration = new Vector2((float)Math.Cos(rotation) * power, (float)Math.Sin(rotation) * power);
            velocity = velocity + acceleration;

            //rotate ship
            float rotationDegreePerSec = 360;
            float rotationChange = pad.X * timeElapsed / 1000 * MathHelper.ToRadians(rotationDegreePerSec);
            rotation += rotationChange;
            rotation = MathHelper.WrapAngle(rotation);

            //Clamp to max speed
            if (velocity.Length() > maxVelocity)
            {
                velocity.Normalize();
                velocity = velocity * maxVelocity;
            }

        }

        public void SetDestructionTimer(float timeToLive)
        {
            destructionTimer = timeToLive;
            countDown = true;
        }

        public bool DestructionTime()
        {
            return countDown && destructionTimer < 0;
        }

        public void Draw(SpriteBatch spriteBatch, List<TextureData> textures)
        {
            Draw(spriteBatch, textures, Color.White);
        }

        public void Draw(SpriteBatch spriteBatch, List<TextureData> textures, Color color)
        {
            spriteBatch.Draw(textures[textureIndex].Texture, center, sourceRectangle, color, rotation + offset, rotatingCenter, 1, SpriteEffects.None, 0);
            bool drawAltenateXCenter = false;
            Vector2 altXCenter = new Vector2(center.X, center.Y);

            if (center.X + (Math.Sqrt(2) * textureWidth / 2) > windowMax.X)
            {
                drawAltenateXCenter = true;
                altXCenter.X = center.X - windowMax.X;    
            }

            if (center.X - (Math.Sqrt(2) * textureWidth / 2) < 0)
            {
                drawAltenateXCenter = true;
                altXCenter.X = center.X + windowMax.X;
            }


            if (drawAltenateXCenter)
            {
                spriteBatch.Draw(textures[textureIndex].Texture, altXCenter, sourceRectangle, color, rotation + offset, rotatingCenter, 1, SpriteEffects.None, 0);
            }


            bool drawAltenateYCenter = false;
            Vector2 altYCenter = new Vector2(center.X, center.Y);

            if (center.Y + (Math.Sqrt(2) * textureHeight / 2) > windowMax.Y)
            {
                drawAltenateYCenter = true;
                altYCenter.Y = center.Y - windowMax.Y;
            }

            if (center.Y - (Math.Sqrt(2) * textureHeight / 2) < 0)
            {
                drawAltenateYCenter = true;
                altXCenter.Y = center.Y + windowMax.Y;
            }


            if (drawAltenateYCenter)
            {
                spriteBatch.Draw(textures[textureIndex].Texture, altYCenter, sourceRectangle, color, rotation + offset, rotatingCenter, 1, SpriteEffects.None, 0);
            }


        }


        public Vector2 LaunchPoint(float offset)
        {
            float length = textureHeight / 2;
            length += offset;
            Vector2 vector = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
            vector *= length;
            return center + vector;
        }

        public bool PointToSphereCollider(Vector2 point)
        {
            Vector2 b = point - center;
            int length = (textureWidth + textureHeight) / 4;
            return (b.Length() < length);
        }

        public bool SphereToSphereCollider(Sprite Sphere)
        {
            Vector2 distance = this.Center - Sphere.Center;
        
            return (distance.Length() < this.Radii + Sphere.Radii);
        }

        public Vector2 CalculateImpulse(Sprite target, ref Vector2 reverseImpulse)
        {
            // afstands vector mellem objekter
            Vector2 distance = target.Center - this.Center;

            if (textureIndex == 0) 
                Console.WriteLine("Ship Calc");
            else 
                Console.WriteLine("Meteor Calc");

            
            Console.WriteLine("Distance: " + distance);
            // beregning af den relative vinkel mellem objekterne
            float impactHeading = (float)Math.Atan2(distance.Y, distance.X);
            float heading = (float)Math.Atan2(velocity.Y, velocity.X);
            float relativeImpactHeading = heading - impactHeading;

            Console.WriteLine("Impact heading: " + impactHeading);
            Console.WriteLine("Heading: " + heading);
            Console.WriteLine("Relative impact heading: " + relativeImpactHeading);
            
            // initiering af retur objekter
            Vector2 impulse = new Vector2();
            reverseImpulse = impulse * -1;

            // vi afgiver kun energi hvis vi faktisk har energi på vej ind i object.
            if (Math.Abs(relativeImpactHeading) < MathHelper.PiOver2)
            {
                // beregning af den energi der bliver flyttet til det andet objekt.

                // den er påvirket af hvor ligepå vi rammer
                float impulseTransferConstant = (float)Math.Abs(Math.Cos(relativeImpactHeading));
                
                // beregning af vore nuværende energi
                float energy = 0.5f * (float)Math.Pow(velocity.Length(),2) * mass;

                Console.WriteLine("Energi={0} (0.5 * {1}^2 * {2})", energy, velocity.Length(), mass);
                // lave energi vector 
                distance.Normalize();
                Console.WriteLine(distance);
                impulse = distance * energy * impulseTransferConstant;
                // beregning af den modsatte kraft som vi må blive påvirket af. 
                reverseImpulse = impulse * -1;
            }

            Console.WriteLine("Impulse: " + impulse);
            Console.WriteLine("Reverse impulse:" + reverseImpulse);
            return impulse;
        }

        public void ApplyImpulse(Vector2 impulse)
        {
            if (textureIndex == 0)
                Console.WriteLine("Ship Apply");
            else
                Console.WriteLine("Meteor Apply");
            Console.WriteLine("Impulse: "+impulse);
            
            
            
            // lave vores bevægelsesvektor om til energivektor
            float speed = (float)Math.Sqrt(2 * Math.Sqrt(2) * impulse.Length() / mass);
            if (speed > 0)
            {
                impulse.Normalize();
                impulse *= speed;
                velocity += impulse;
            }
        }

        public override string ToString()
        {
            return center + "\n" + velocity;
        }
        #endregion

        #region private Members

        private void calculateSourceRectangle()
        {
            sourceRectangle.X = textureWidth * spriteIndex % rows;
            sourceRectangle.Y = textureHeight * spriteIndex / columns;
        }

        #endregion

        #region Serializers

        public byte[] Serialize(int id)
        {
            byte[] packet = new byte[20];
            Buffer.BlockCopy(BitConverter.GetBytes(id), 0, packet, 0, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(textureIndex), 0, packet, 4, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(rotation), 0, packet, 8, 4);//(float)Math.Atan2(velocity.X, velocity.Y)), 0, packet, 8, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(center.X), 0, packet, 12, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(center.Y), 0, packet, 16, 4);
            return packet;
        }

        public byte[] SerializeFull(int id)
        {
            byte[] packet = new byte[56];
            Buffer.BlockCopy(BitConverter.GetBytes(id), 0, packet, 0, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(textureIndex), 0, packet, 4, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(textureWidth), 0, packet, 8, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(textureHeight), 0, packet, 12, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(columns), 0, packet, 16, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(rows), 0, packet, 20, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(center.X), 0, packet, 24, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(center.Y), 0, packet, 28, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(velocity.X), 0, packet, 32, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(velocity.Y), 0, packet, 36, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(offset), 0, packet, 40, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(mass), 0, packet, 44, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(rotationSpeed), 0, packet, 48, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(destructionTimer), 0, packet, 52, 4);
            return packet;
        }

        public void Deserialize(byte[] buffer)
        {
            if (buffer.Length != 20)
            {
                throw new IndexOutOfRangeException();
            }
            textureIndex = BitConverter.ToInt32(buffer,4);
            rotation = BitConverter.ToSingle(buffer, 8);
            velocity.X = (float)Math.Cos(BitConverter.ToSingle(buffer, 8));
            velocity.Y = -(float)Math.Sin(BitConverter.ToSingle(buffer, 8));
            center.X = BitConverter.ToSingle(buffer,12);
            center.Y = BitConverter.ToSingle(buffer, 16);
        }

        public byte[] LeftChild()
        {
            byte[] data = new byte[32];

            float heading = (float)Math.Atan2(velocity.Y,velocity.X);
            heading -= MathHelper.ToRadians(45);
            heading = MathHelper.WrapAngle(heading);
            commonData(ref data);
            Buffer.BlockCopy(BitConverter.GetBytes(heading), 0, data, 12, 4); //heading

            return data;
        }
        
        public byte[] RightChild()
        {
            byte[] data = new byte[32];
            float heading = (float)Math.Atan2(velocity.Y, velocity.X);
            heading += MathHelper.ToRadians(45);
            heading = MathHelper.WrapAngle(heading);
            commonData(ref data);
            Buffer.BlockCopy(BitConverter.GetBytes(heading), 0, data, 12, 4); //heading
            
            return data;
        }

        private void commonData(ref byte[] data)
        {
            Buffer.BlockCopy(BitConverter.GetBytes(textureIndex+1), 0, data, 0, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(center.X), 0, data, 4, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(center.Y), 0, data, 8, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(velocity.Length() * 1f), 0, data, 16, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(mass/2), 0, data, 20, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(rotationSpeed), 0, data, 24, 4);
            Buffer.BlockCopy(BitConverter.GetBytes((float)0), 0, data, 28, 4);
        }
        #endregion
    }
}
